package roborallyimplementation;
import roborally.IRobot;
import be.kuleuven.cs.som.annotate.*;

/**
 * Een klasse van robots die dienen als pionnen voor het spel RoboRally.
 * 
 * @invar	De robot heeft een geldige positie.
 * 			| isValidPosition(getXCoordinate(), getYCoordinate())
 * @invar	De robot heeft een geldig resterend energieniveau.
 * 			| canHaveAsEnergyLevel(getEnergyLevel())
 * @invar	De oriëntatie van de robot is altijd een rechte hoek.
 * 			| isRightAngle(getOrientation()) 
 * 
 * @author	Thomas Vochten (1ba inform) en Daan Daele (1ba inform)
 * @version	1.0
 *
 */
public class Robot implements IRobot {
	
	/**
	 * Initialiseert een robot met gegeven x-coördinaat, y-coördinaat, oriëntatie en energieniveau.
	 * 
	 * @param	xcoord
	 * 			De horizontale coördinaat van de nieuwe robot.
	 * @param	ycoord
	 * 			De verticale coördinaat van de nieuwe robot.
	 * @param	orientation
	 * 			De oriëntatie van de nieuwe robot gegeven in graden.
	 * @param	energylevel
	 * 			Het initiële energieniveau van de nieuwe robot geven in watt-seconden.
	 * @pre		Het gegeven energieniveau is een aanvaardbaar energieniveau.
	 * 			|	new.canHaveAsEnergyLevel(energylevel)
	 * @post	De x-coördinaat van de nieuwe robot is gelijk aan de gegeven x-coördinaat.
	 * 			|	new.getXCoordinate() == xcoord
	 * @post	De y-coördinaat van de nieuwe robot is gelijk aan de gegeven y-coördinaat.
	 * 			|	new.getYCoordinate() == ycoord
	 * @post	Als de gegeven oriëntatie een rechte hoek is, is de oriëntatie van de nieuwe robot gelijk aan de gegeven oriëntatie. Anders is de oriëntatie 0 graden.
	 * 			|	if (isStraightAngle(orientation) then new.getOrientation() == orientation
	 * 			|		else new.getOrientation() == 0
	 * @post	Het energieniveau van de nieuwe robot is gelijk aan het gegeven energieniveau.
	 * 			|	new.getEnergyLevel() == energylevel
	 * @throws	IllegalPositionException("Attempt to assign an illegal position to a robot.", xcoord, ycoord)
	 * 			De gegeven positie is geen aanvaardbare positie
	 * 			| ! isValidPosition(xcoord, ycoord)
	 * 			
	 */
	@Raw
	public Robot(long xcoord, long ycoord, int orientation, double energylevel) throws IllegalPositionException
	{
		assert isValidEnergyLevel(energylevel);
		if (!isValidPosition(xcoord, ycoord))
			throw new IllegalPositionException("User attempted to add a robot on an illegal position.",xcoord,ycoord);
		if (! isRightAngle(orientation))
			setOrientation(0);
		else setOrientation(orientation);
		setEnergyLevel(energylevel);
		setXCoord(xcoord);
		setYCoord(ycoord);
	}
	
	/**
	 * Initialiseert een robot met gegeven positie, een oriëntatie van 0 graden en het maximale energieniveau.
	 * 
	 * @param	xcoord
	 * 			De horizontale coördinaat van de nieuwe robot.
	 * @param	ycoord
	 * 			De verticale coördinaat van de nieuwe robot.
	 * @effect	De nieuwe robot krijgt de gegeven positie als positie, een oriëntatie van 0 graden als oriëntatie en het maximale energieniveau als energieniveau.
	 * 			|	this(xcoord, ycoord, 0, 20000)
	 */
	@Raw
	public Robot(long xcoord, long ycoord)
	{
		this(xcoord, ycoord, 0, 20000);
	}
	
	/**
	 * Initialiseert een robot met de gegeven coördinatenrij, oriëntatie en energieniveau.
	 * 
	 * @param	coords
	 * 			De coördinatenrij voor de nieuwe robot.
	 * @param	orientation
	 * 			De oriëntatie van de nieuwe robot gegeven in graden
	 * @param	energylevel
	 * 			Het energieniveau van de nieuwe robot.
	 * @effect	De nieuwe robot wordt geïnitialiseerd als waren de coördinaten opgegeven als aparte getallen in plaats van als een rij.
	 * 			|	this(coords[0], coords[1], orientation, energylevel
	 */
	@Raw
	public Robot(long[] coords, int orientation, int energylevel) throws IllegalPositionException
	{
		this(coords[0], coords[1], orientation, energylevel);
	}
	
	/**
	 * Initialiseert een robot met de gegeven coördinatenrij, een oriëntatie van 0 graden en het maximale energieniveau.
	 * 
	 * @param	coords
	 * 			De coördinatenrij voor de nieuwe robot.
	 * @effect	De nieuwe robot krijgt de gegeven positie als positie, een oriëntatie van 0 graden als oriëntatie en het maximale energieniveau als energieniveau. 
	 * 			|	this(coords[0], coords[1], 0, 20000)
	 */
	@Raw
	public Robot(long[] coords)
	{
		this(coords[0], coords[1], 0, 20000);
	}
	
	/**
	 * Initialiseert een robot met als positie de linkerbovenhoek van het bord, een oriëntatie van 0 graden en het maximale energieniveau.
	 * 
	 * @effect	De nieuwe robot krijgt als x-coördinaat en als y-coördinaat 0, een oriëntatie van 0 graden en het maximale energieniveau.
	 * 			|	this(0, 0, 0, 20000)
	 */
	@Raw
	public Robot()
	{
		this(0, 0, 0, 20000);
	}
	
	/**
	 * Geeft de x-coördinaat van de robot terug.
	 * 		De x-coördinaat geeft de positie van de robot op de horizontale as aan.
	 */
	@Basic
	public long getXCoordinate()
	{
		return xcoord;
	}
	
	/**
	 * Geeft de y-coördinaat van de robot terug.
	 * 		De y-coördinaat geeft de positie van de robot op de verticale as aan.
	 */
	@Basic
	public long getYCoordinate()
	{
		return ycoord;
	}
	
	/**
	 * Geeft de coördinaten van de robot terug onder de vorm van een array van type long die twee elementen bevat: de x-coördinaat en de y-coördinaat.
	 * 		Het eerste element is de x-coördinaat, de tweede de y-coördinaat.
	 */
	@Basic
	public long[] getCoordinates()
	{
		long[] coords = {getXCoordinate(), getYCoordinate()};
		return coords;
	}
	
	/**
	 * Vergelijkt twee coördinaten met elkaar.
	 * 
	 * @param	coordinate
	 * 			De coördinaat waar het resultaat betrekking op heeft.
	 * 				Als het resultaat bijvoorbeeld negatief is, komt dit omdat othercoordinate groter is dan coordinate.
	 * @param	othercoordinate
	 * 			De coördinaat die vergeleken zal worden met de hoofdcoördinaat (zijnde de eerste parameter).
	 * @return	Het verschil in waarde tussen de twee coördinaten.
	 * 			|	result == coordinate - othercoordinate
	 * @throws	IllegalPositionException("Attempt to compare illegal coordinates.", coordinate, othercoordinate)
	 * 			Eén van de gegeven coördinaten is geen geldige coördinaat.
	 * 			| ! (isValidCoordinate(coordinate) && isValidCoordinate(othercoordinate))
	 */
	@Raw
	public long compareCoordinates(long coordinate, long othercoordinate) throws IllegalPositionException
	{
		if (! (isValidCoordinate(coordinate) && isValidCoordinate(othercoordinate)))
			throw new IllegalPositionException("Attempt to compare illegal coordinates.", coordinate, othercoordinate);
		return coordinate - othercoordinate;
	}
	
	/**
	 * Geeft de bovenrand van de horizontale as terug.
	 */
	@Basic @Immutable
	public long getUpperXBound()
	{
		return maxX;
	}
	
	/**
	 * Geeft de bovenrand van de verticale as terug.
	 */
	@Basic @Immutable
	public long getUpperYBound()
	{
		return maxY;
	}
	
	
	/**
	 * Hulpmethode voor move(): berekent de richting waarin de robot zich zal bewegen.
	 * 
	 * @param	orientation
	 * 			De oriëntatie die gebruikt zal worden in de berekening.
	 * @return	Een array met lengte 2 waarin de richting vervat zit.
	 */
	@Model
	private long[] calculateDirection(int orientation)
	{
		long[] toreturn = {0, 0};
		if (orientation == 0)
			toreturn[0] = 1;
		if (orientation == 90)
			toreturn[1] = -1;
		if (orientation == 180)
			toreturn[0] = -1;
		if (orientation == 270)
			toreturn[1] = 1;
		return toreturn;
	}
	
	/**
	 * Geeft aan of de meegegeven coördinaat een geldige coördinaat is.
	 * 
	 * @param	coord
	 * 			De coördinaat die geëvalueerd zal worden.
	 * @return	Waar als en slechts als de coördinaat positief is en kleiner dan de bovenrand van de horizontale as of de bovenrand van de verticale as.
	 * 			|	result == coord >= 0 && (coord <= getUpperXBound() || coord <= getUpperYBound())
	 */
	
	@Raw
	public boolean isValidCoordinate(long coord)
	{
		return coord >= 0 && (coord <= getUpperXBound() || coord <= getUpperYBound());
	}
	
	/**
	 * Geeft aan of de meegegeven positie een aanvaardbare positie is.
	 * 
	 * @param	xcoord
	 * 			De horizontale coördinaat van de robot.
	 * @param	ycoord
	 * 			De verticale coördinaat van de robot.
	 * @return	Waar als en slechts als zowel de horizontale als verticale coördinaten positief zijn en kleiner of gelijk aan de grenzen aangegeven door het spelbord.
	 * 			|	result == (xcoord >= 0 && xcoord <= getUpperXBound()) && (ycoord >= 0 && ycoord <= getUpperYBound())
	 */
	
	@Raw
	public boolean isValidPosition(long xcoord, long ycoord)
	{
		return (xcoord >= 0 && xcoord <= getUpperXBound()) && (ycoord >= 0 && ycoord <= getUpperYBound());
	}
	
	/**
	 * Geeft aan of de meegegeven positie een aanvaardbare positie is.
	 * 
	 * @param	coords
	 * 			Het coördinatenpaar van de robot.
	 * @return	Waar als en slechts als de gegeven coördinatenrij twee-dimensionaal is en zowel de x-coördinaat en de y-coördinaat positief zijn en kleiner zijn dan de grenzen aangegeven door het spelbord.
	 * 			|	result == coords.length == 2 && (coords[0] >= 0 && coords[0] <= getUpperXBound()) && (coords[1] >= 0 && coords[1] <= getUpperYBound())
	 */
	@Raw
	public boolean isValidPosition(long[] coords)
	{
		return coords.length == 2 && (coords[0] >= 0 && coords[0] <= getUpperXBound()) && (coords[1] >= 0 && coords[1] <= getUpperYBound());
	}
	
	/**
	 * Geeft aan of de robot zich op dit moment kan bewegen.
	 * 
	 * @return	Waar als en slechts de robot minstens de nodige energie om zich te bewegen in watt-seconden beschikbaar heeft.
	 * 			|	result == getEnergyLevel() >= MOVE_COST
	 */
	public boolean canMove()
	{
		return this.getEnergyLevel() >= MOVE_COST;
	}
	
	/**
	 * Geeft aan of de meegegeven robot naast deze robot gelegen is.
	 * 
	 * @param	other
	 * 			De robot waarover men wil weten of die naast deze robot ligt.
	 * @return	Waar als en slechts als de absolute waarde van het verschil van exact één coördinaat één is.
	 * 			|	result == ( (Math.abs(compareCoordinates(this.getXCoordinate(), other.getXCoordinate())) == 1 && Math.abs(this.getYCoordinate(), other.getYCoordinate()) == 0)
				|	|| (this.getXCoordinate(), other.getXCoordinate()) == 0 && Math.abs(this.getYCoordinate(), other.getYCoordinate()) == 1))
	 */
	public boolean isNextTo(Robot other)
	{
		long differencex = compareCoordinates(this.getXCoordinate(), other.getXCoordinate());
		long differencey = compareCoordinates(this.getYCoordinate(), other.getYCoordinate());
		return (Math.abs(differencex) == 1 && Math.abs(differencey) == 0)
				|| (Math.abs(differencex) == 0 && Math.abs(differencey) == 1);
	}
	
	public static final double MOVE_COST = 500;
	
	/**
	 * Beweegt de robot voorwaarts in de richting van zijn oriëntatie.
	 * 
	 * @pre		De robot heeft voldoende energie om zich te kunnen bewegen.
	 * 			|	this.canMove()
	 * @post	Als de robot een oriëntatie heeft van 0 graden heeft hij zich naar rechts bewogen.
	 * 			|	if (this.getOrientation() == 0) then new.getXCoordinate() == this.getXCoordinate() + 1
	 * @post	Als de robot een oriëntatie heeft van 90 graden heeft hij zich naar boven bewogen.
	 * 			|	if (this.getOrientation() == 90) then new.getYCoordinate() == this.getYCoordinate() - 1
	 * @post	Als de robot een oriëntatie heeft van 180 graden heeft hij zich naar links bewogen.
	 * 			|	if (this.getOrientation() == 180) then new.getXCoordinate() == this.getXCoordinate() - 1
	 * @post	Als de robot een oriëntatie heeft van 270 graden heeft hij zich naar beneden bewogen.
	 * 			|	if (this.getOrientation() == 270) then new.getYCoordinate() == this.getYCoordinate() + 1
	 * @throws	IllegalPositionException
	 * 			De beweging zou resulteren in een onaanvaardbare positie.
	 * 			|	! isValidPosition(new.getXCoordinate(), new.getYCoordinate())
	 */
	public void move() throws IllegalPositionException
	{
		assert this.canMove();
		long[] direction = calculateDirection(getOrientation());
		long newx = this.getXCoordinate() + direction[0];
		long newy = this.getYCoordinate() + direction[1];
		if (! isValidPosition(newx, newy))
			throw new IllegalPositionException(this, "Attempted to make an illegal move.", newx, newy);
		this.setPosition(newx, newy);
		this.setEnergyLevel(this.getEnergyLevel() - MOVE_COST);
	}
	
	/**
	 * Beweegt deze robot en de gegeven robot naar elkaar toe.
	 * 
	 * @param	other
	 * 			De robot waar deze robot naartoe zal bewegen, en tevens ook de robot die naar deze robot toe zal bewegen.
	 * @post	Als beide robots dezelfde plaats deelden voor een oproep van deze methode, heeft één van beiden zich bewogen
	 * 			op voorwaarde dat minstens één voldoende energie heeft om die beweging uit te voeren.
	 * 			|	if (this.canMove() || other.canMove()) then new.isNextTo((new other))
	 */
	public void moveToOtherRobot(Robot other)
	{
		boolean triedToActOnceBefore = false;
		long differencex = compareCoordinates(this.getXCoordinate(), other.getXCoordinate());
		long differencey = compareCoordinates(this.getYCoordinate(), other.getYCoordinate());
		if (differencex == 0 && differencey == 0 && this.canMove())
		{
			this.moveAwayFromSpot();
		}
		if (other.canMove() && (this.getXCoordinate() == other.getXCoordinate() && this.getYCoordinate() == other.getYCoordinate()))
		{
			other.moveAwayFromSpot();
			return;
		}
		while (! this.isNextTo(other) && ! triedToActOnceBefore )
			{
				int currentorientation = this.getOrientation();
				long[] currentposition = this.getCoordinates();
				this.moveTowards(other.getXCoordinate(), other.getYCoordinate());
				if (this.getOrientation() == currentorientation && this.getXCoordinate() == currentposition[0] && this.getYCoordinate() == currentposition[1])
					triedToActOnceBefore = true;
				if (other.canMove() && ! this.isNextTo(other))
					other.moveTowards(this.getXCoordinate(), this.getYCoordinate());
			}
		triedToActOnceBefore = false;
		while (! this.isNextTo(other) && ! triedToActOnceBefore)
		{
			int otherorientation = other.getOrientation(); long[] otherposition = other.getCoordinates();
			other.moveTowards(this.getXCoordinate(), this.getYCoordinate());
			if (other.getOrientation() == otherorientation && other.getXCoordinate() == otherposition[0] && other.getYCoordinate() == otherposition[1])
				triedToActOnceBefore = true;
		}
	}
	
	/**
	 * Hulpmethode voor moveToOtherRobot()
	 */
	@Raw
	private void moveAwayFromSpot()
	{
		if (( !(this.getXCoordinate() == 0 || this.getYCoordinate() == 0)) 
				&& ( !(this.getXCoordinate() == this.getUpperXBound() || this.getYCoordinate() == this.getUpperYBound())))
		{
				this.move();
				return;
		}
		else
		{
			if (this.getOrientation() == 90 && isValidPosition(this.getXCoordinate(), this.getYCoordinate() - 1))
			{
				this.move();
				return;
			}
			else if (this.getOrientation() == 90 && isValidPosition(this.getXCoordinate() - 1, this.getYCoordinate()))
			{
				this.turnLeft();
				if (this.canMove())
					this.move();
				return;
			}
			else if (this.getOrientation() == 90 && isValidPosition(this.getXCoordinate() + 1, this.getYCoordinate()))
			{
				this.turnRight();
				if (this.canMove())
					this.move();
				return;
			}
			else if (this.getOrientation() == 270 && isValidPosition(this.getXCoordinate(), this.getYCoordinate() + 1))
			{
				this.move();
				return;
			}
			else if (this.getOrientation() == 270 && isValidPosition(this.getXCoordinate() - 1, this.getYCoordinate()))
			{
				this.turnRight();
				if (this.canMove())
					this.move();
				return;
			}
			else if (this.getOrientation() == 270 && isValidPosition(this.getXCoordinate() + 1, this.getYCoordinate()))
			{
				this.turnLeft();
				if (this.canMove())
					this.move();
				return;
			}
			else if (this.getOrientation() == 0 && isValidPosition(this.getXCoordinate() + 1, this.getYCoordinate()))
			{
				this.move();
				return;
			}
			else if (this.getOrientation() == 0 && isValidPosition(this.getXCoordinate(), this.getYCoordinate() - 1))
			{
				this.turnLeft();
				if (this.canMove())
					this.move();
			}
			else if (this.getOrientation() == 0 && isValidPosition(this.getXCoordinate(), this.getYCoordinate() + 1))
			{
				this.turnRight();
				if (this.canMove())
					this.move();
				return;
			}
			else if (this.getOrientation() == 180 && isValidPosition(this.getXCoordinate() - 1, this.getYCoordinate()))
			{
				this.move();
				return;
			}
			else if (this.getOrientation() == 180 && isValidPosition(this.getXCoordinate(), this.getYCoordinate() - 1))
			{
				this.turnRight();
				if (this.canMove())
					this.move();
				return;
			}
			else if (this.getOrientation() == 180 && isValidPosition(this.getXCoordinate(), this.getYCoordinate() + 1))
			{
				this.turnLeft();
				if (this.canMove())
					this.move();
				return;
			}
		}
	}
	
	/**
	 * Beweegt de robot naar een bepaalde coördinaat toe. De robot voert één actie uit per oproep van deze methode (op voorwaarde dat
	 * deze robot al niet gepositioneerd is op het gegeven coördinatenpaar): ofwel draait de robot zich, ofwel beweegt de robot zich voorwaarts.
	 * 
	 * @param	xcoord
	 * 			De horizontale coördinaat waar de robot naartoe moet gaan.
	 * @param	ycoord
	 * 			De verticale coördinaat waar de robot naartoe moet gaan.
	 * @effect	De robot heeft zich bewogen of gedraaid.
	 * 			|	move() || turnLeft() || turnRight()
	 */
	public void moveTowards(long xcoord, long ycoord)
	{
		long[] direction = calculateDirection(this.getOrientation());
		if (this.getXCoordinate() == xcoord && this.getYCoordinate() == ycoord)
			return;
		if (compareCoordinates(this.getXCoordinate(), xcoord) < 0 && direction[0] == 1 && direction[1] == 0)
		{
			if (this.canMove())
				move();
			return;
		}
		else if(compareCoordinates(this.getXCoordinate(), xcoord) > 0 && direction[0] == -1 && direction[1] == 0)
		{
			if (this.canMove())
				move();
			return;
		}
		else if (compareCoordinates(this.getYCoordinate(), ycoord) < 0 && direction[0] == 0 && direction[1] == 1)
		{
			if (this.canMove())
				move();
			return;
		}
		else if (compareCoordinates(this.getYCoordinate(), ycoord) > 0 && direction[0] == 0 && direction[1] == -1)
		{
			if (this.canMove())
				move();
			return;
		}
		else if (this.canTurn())
			makeTurn(xcoord, ycoord, direction);
	}
	/**
	 * Geeft de minimale energie nodig voor het bewegen van de robot naar een specifieke positie.
	 * 
	 * @param	xcoord
	 * 			De horizontale coordinaat.
	 * @param	ycoord
	 * 			De verticale coordinaat.
	 * @effect	De totale kost van de bewegingen en de draaibewegingen wordt berekend.
	 * 			|	result == calculateMovementCost(xcoord, ycoord) + calculateTurnCost(xcoord, ycoord)
	 */
	public double minEnergy(long xcoord, long ycoord)
	{
		if (! isValidPosition(xcoord, ycoord))
			throw new IllegalPositionException("Cannot calculate the energy required to move to an illegal position", xcoord, ycoord);
		return calculateMovementCost(xcoord, ycoord) + calculateTurnCost(xcoord, ycoord);
	}
	
	/** Berekent de benodigde energie om naar een bepaalde positie te bewegen. Deze methode houdt geen rekening met draaibewegingen die de robot moet uitvoeren.
	 * 
	 * @param	xcoord
	 * 			De horizontale coördinaat waar de robot naartoe zou bewegen.
	 * @param	ycoord
	 * 			De vertical coördinaat waar de robot naartoe zou bewegen.
	 * @return	Het verschil in absolute waarde tussen de huidige coördinaten van de robot vermenigvuldigd met de energie die nodig is om vooruit te bewegen.
	 * 			|	result == Math.abs(compareCoordinates(this.getXCoordinate(), xcoord)) * MOVE_COST + Math.abs(compareCoordinates(this.getYCoordinate(), ycoord) * MOVE_COST)
	 */
	@Model
	private double calculateMovementCost(long xcoord, long ycoord)
	{
		return (Math.abs(compareCoordinates(this.getXCoordinate(), xcoord)) * MOVE_COST) + (Math.abs(compareCoordinates(this.getYCoordinate(), ycoord) * MOVE_COST));
	}
	
	/**
	 * Berekent de energie die opgebruikt wordt door draaibewegingen mocht de robot zich naar deze coördinaten toe bewegen.
	 * 
	 * @param	xcoord
	 * 			De horizontale coördinaat waar de robot naartoe zou bewegen.
	 * @param	ycoord
	 * 			De verticale coördinaat waar de robot naartoe zou bewegen.
	 * @return	Afhankelijk van de oriëntatie van de robot ofwel 0, de energie die nodig is om te draaien of twee keer de energie nodig om te draaien.
	 * 			|	result <= 2 * TURN_COST && result >= 0
	 */
	private double calculateTurnCost(long xcoord, long ycoord)
	{
		long differencex = compareCoordinates(this.getXCoordinate(), xcoord);
		long differencey = compareCoordinates(this.getYCoordinate(), ycoord);
		if (this.getOrientation() == 0 && differencex > 0)
			return 2 * TURN_COST;
		else if (this.getOrientation() == 0 && Math.abs(differencey) >= 1 && differencex <= 0)
			return TURN_COST;
		else if (this.getOrientation() == 90 && differencey < 0)
			return 2 * TURN_COST;
		else if (this.getOrientation() == 90 && Math.abs(differencex) >= 1 && differencey >= 0)
			return TURN_COST;
		else if (this.getOrientation() == 180 && differencex < 0)
			return 2 * TURN_COST;
		else if (this.getOrientation() == 180 && Math.abs(differencey) >= 1 && differencex >= 0)
			return TURN_COST;
		else if (this.getOrientation() == 270 && differencey > 0)
			return 2 * TURN_COST;
		else if (this.getOrientation() == 270 && Math.abs(differencex) >= 1 && differencey <= 0)
			return TURN_COST;
		return 0;
	}
	
	/**
	 * Hulpmethode voor de constructoren.
	 */
	@Raw
	private void setPosition(long xcoord, long ycoord)
	{
		this.xcoord = xcoord;
		this.ycoord = ycoord;
	}
	
	/**
	 * Hulpmethode voor move()
	 */
	@Raw
	private void setXCoord(long xcoord)
	{
		this.xcoord = xcoord;
	}
	
	/**
	 * Hulpmethode voor move()
	 */
	@Raw
	private void setYCoord(long ycoord)
	{
		this.ycoord = ycoord;
	}
	
	private long xcoord;
	private long ycoord;
	private long maxX = Long.MAX_VALUE;
	private long maxY = Long.MAX_VALUE;
	
	/**
	 * Geeft de oriëntatie van de robot terug uitgedrukt in graden (met 90 graden als rechte hoek). 
	 * 		0 graden betekent dat de robot naar rechts kijkt. De oriëntatie gaat in tegenwijzerzin: 0 graden is rechts, 90 graden is naar boven, 180 graden is links, 270 graden is naar beneden.
	 */
	@Basic
	public int getOrientation()
	{
		return orientation;
	}
	
	/**
	 * Geeft aan of de gegeven hoek een rechte hoek is.
	 * 
	 * @param	orientation
	 * 			De hoek die getest zal worden.
	 * @return	Waar als en slechts als de gegeven hoek een rechte hoek is.
	 * 			|	result == (orientation == 0 || orientation == 90 || orientation == 180 || orientation == 270)
	 */
	public static boolean isRightAngle(int orientation)
	{
		return orientation == 0 || orientation == 90 || orientation == 180 || orientation == 270;
	}
	
	/**
	 * Geeft aan of de robot zich kan draaien.
	 * 
	 * @return	Waar als en slechts als de robot meer dan de nodige energie om te draaien in watt-seconden beschikbaar heeft.
	 * 			|	result == this.getEnergyLevel() >= TURN_COST
	 */
	public boolean canTurn()
	{
		return getEnergyLevel() >= TURN_COST;
	}
	
	public static final double TURN_COST = 100;
	
	/**
	 * Draait de robot naar links.
	 * 
	 * @post	De oriëntatie, uitgedrukt in graden, is vermeerderd met 90 graden. Mocht de oriëntatie als resultaat van een oproep van deze methode 360 zijn, wordt het terug naar 0 gebracht.
	 * 			|	new.getOrientation() == (this.getOrientation() + 90) % 360
	 */
	public void turnLeft()
	{
		assert this.canTurn();
		setOrientation((getOrientation() + 90) % 360);
		setEnergyLevel(getEnergyLevel() - TURN_COST);
	}
	
	/**
	 * Draait de robot naar rechts.
	 * 
	 * @post	De oriëntatie, uitgedrukt in graden, is verminderd met 90 graden. Als de huidige oriëntatie 0 graden is, wordt de nieuwe oriëntatie 270 graden.
	 * 			|	if (this.getOrientation() == 0) then new.getOrientation() == 270
	 * 			|	else new.getOrientation() == this.getOrientation - 90
	 */
	public void turnRight()
	{
		assert this.canTurn();
		if (getOrientation() == 0)
			setOrientation(270);
		else
		{
		setOrientation(getOrientation() - 90);
		}
		setEnergyLevel(getEnergyLevel() - TURN_COST);
	}
	
	/**
	 * Afhankelijk van de gegeven coördinaten en de huidige
	 * oriëntatie wordt de meest efficiënte draairichting berekend en draait de robot ook in deze ricthing, tenzij de robot niet genoeg energie heeft hiervoor.
	 * 
	 * @post	De robot heeft zich gedraaid als de robot daar genoeg energie voor had.
	 * 			|	if (this.canTurn()) then new.getOrientation == this.getOrientation() + 90 || this.getOrientation() - 90 || this.getOrientation()
	 */
	@Raw
	private void makeTurn(long xcoord, long ycoord, long[] direction)
	{
		long differencex = compareCoordinates(this.getXCoordinate(), xcoord);
		long differencey = compareCoordinates(this.getYCoordinate(), ycoord);
		if (differencex == 0 && differencey == 0)
			return;
		else if (direction[0] == -1 && differencey < 0 && differencex <= 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[0] == -1 && differencey > 0 && differencex <= 0)
		{
			if (this.canTurn())
				turnRight();
			return;
		}
		else if (direction[0] == -1 && differencex <= 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[0] == 1 && differencey < 0 && differencex >= 0)
		{
			if (this.canTurn())
				turnRight();
			return;
		}
		else if (direction[0] == 1 && differencey > 0 && differencex >= 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[0] == 1 && differencex >= 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[1] == -1 && differencey <= 0 && differencex > 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[1] == -1 && differencey <= 0 && differencex < 0)
		{
			if (this.canTurn())
				turnRight();
			return;
		}
		else if (direction[1] == -1 && differencey <= 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[1] == 1 && differencey >= 0 && differencex > 0)
		{
			if (this.canTurn())
				turnRight();
			return;
		}
		else if (direction[1] == 1 && differencey >= 0 && differencex < 0)
		{
			if (this.canTurn())
				turnLeft();
			return;
		}
		else if (direction[1] == 1 && differencey >= 0)
		{
			if (this.canTurn())
				turnLeft();
		}
			
			
	}
	
	/**
	 * Hulpmethode voor de turnLeft() en turnRight() methoden.
	 */
	@Raw
	private void setOrientation(int orientation)
	{
		this.orientation = orientation;
	}
	
	private int orientation;
	
	/**
	 * Geeft de resterende energie van de robot terug in watt-seconden.
	 */
	@Basic
	public double getEnergyLevel()
	{
		return energy;
	}
	
	/**
	 * Geeft de resterende energie van de robot terug als percentage.
	 * 
	 * @return	Het resterend energieniveau als percentage.
	 * 			|	result == (getEnergyLevel()/getMaxEnergy())*100
	 */
	public double getEnergyPercentage()
	{
		return (getEnergyLevel()*100/MAX_ENERGY);
	}
	
	/**
	 * Geeft aan of het energieniveau meegegeven als parameter een aanvaardbaar energieniveau is.
	 * 
	 * @param	energylevel
	 * 			Het energieniveau dat getest zal worden.
	 * @return	Waar als en slechts als het voorgestelde energieniveau positief is en als dat energieniveau het hoogst mogelijke energieniveau is of daaronder ligt, anders vals.
	 * 			|	result == (testenergy >= 0) && (testenergy <= getMaxEnergy())
	 */
	@Raw
	public static boolean isValidEnergyLevel(double energylevel)
	{
		return (energylevel >= 0) && (energylevel <= MAX_ENERGY);
	}
	
	/**
	 * Herlaadt de robot met de gegeven eenheden energie in watt-seconden.
	 * 
	 * @param	energylevel
	 * 			De energie waarmee de robot zal herladen worden.
	 * @pre		Deze herlaadbeurt houdt het energieniveau binnen aanvaardbare grenzen.
	 * 			|	canHaveAsEnergyLevel(this.getEnergyLevel() + energylevel)
	 * @post	Het nieuwe energieniveau is gelijk aan het huidige energieniveau met het gegeven energieniveau daarbij opgeteld.
	 * 			|	new.getEnergyLevel() == this.getEnergyLevel() + energylevel
	 */
	public void recharge(double energylevel)
	{
		assert isValidEnergyLevel(this.getEnergyLevel() + energylevel);
		setEnergyLevel(this.getEnergyLevel() + energylevel);
	}
	
	/**
	 * Hulpmethode voor de constructoren.
	 */
	@Raw
	private void setEnergyLevel(double energylevel)
	{
		this.energy = energylevel;
	}
	
	private double energy;
	public static final double MAX_ENERGY = 20000;
}
